import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:Bloomee/screens/widgets/snackbar.dart';
import 'package:Bloomee/services/db/GlobalDB.dart';
import 'package:Bloomee/services/db/bloomee_db_service.dart';
import 'package:Bloomee/services/m3u_processor.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';

/// Service for importing and exporting playlists and media items.
/// This service currently supports JSON format but can be extended to support other formats.
class ImportExportService {
  /// Checks if a playlist with the given name already exists in the library.
  ///
  /// [playlistName] - The name of the playlist to check.
  /// Returns `true` if the playlist exists, otherwise `false`.
  static Future<bool> isPlaylistExists(String playlistName) async {
    // Fetch all playlists from the library.
    final _list = await BloomeeDBService.getPlaylists4Library();
    for (final playlist in _list) {
      if (playlist.playlistName == playlistName) {
        return true;
      }
    }
    return false;
  }

  /// Exports a playlist to a JSON file.
  ///
  /// [playlistName] - The name of the playlist to export.
  /// Returns the file path of the exported JSON file, or `null` if an error occurs.
  static Future<String?> exportPlaylist(String playlistName,
      {String? filePath}) async {
    final mediaPlaylistDB = await BloomeeDBService.getPlaylist(playlistName);
    if (mediaPlaylistDB != null) {
      try {
        // Fetch all media items in the playlist.
        List<MediaItemDB>? playlistItems =
            await BloomeeDBService.getPlaylistItems(mediaPlaylistDB);
        final packageInfo = await PackageInfo.fromPlatform();

        if (playlistItems != null) {
          // Prepare the playlist data for export.
          final Map<String, dynamic> playlistMap = {
            '_meta': {
              'generated_by':
                  'Bloomee - Open Source Music Streaming Application',
              'version':
                  'v${packageInfo.version}+${int.parse(packageInfo.buildNumber) % 1000}',
              'exportedAt': DateTime.now().toIso8601String(),
              'note':
                  'This file is automatically generated by Bloomee and is intended solely for importing playlists within the application. Manual modification of this file is strongly discouraged, as it may result in errors, data inconsistency, or failed imports. For support or more information, please visit: https://github.com/HemantKArya/BloomeeTunes.',
            },
            'playlistName': mediaPlaylistDB.playlistName,
            'mediaRanks': mediaPlaylistDB.mediaRanks,
            'mediaItems': playlistItems.map((e) => e.toMap()).toList(),
          };

          // Write the playlist data to a JSON file.
          final path = await writeToJSON(
              '${mediaPlaylistDB.playlistName}_BloomeePlaylist.json',
              playlistMap,
              path: filePath);
          log("Playlist exported successfully", name: "FileManager");
          return path;
        }
      } catch (e) {
        log("Error exporting playlist: $e");
        return null;
      }
    } else {
      log("Playlist not found", name: "FileManager");
    }
    return null;
  }

  /// Exports a single media item to a JSON file.
  ///
  /// [mediaItemDB] - The media item to export.
  /// Returns the file path of the exported JSON file, or `null` if an error occurs.
  static Future<String?> exportMediaItem(MediaItemDB mediaItemDB) async {
    try {
      // Prepare the media item data for export.
      final Map<String, dynamic> mediaItemMap = mediaItemDB.toMap();
      final packageInfo = await PackageInfo.fromPlatform();
      mediaItemMap['_meta'] = {
        'generated_by': 'Bloomee - Open Source Music Streaming Application',
        'version':
            'v${packageInfo.version}+${int.parse(packageInfo.buildNumber) % 1000}',
        'exportedAt': DateTime.now().toIso8601String(),
        'note':
            'This file is automatically generated by Bloomee and is intended solely for importing playlists within the application. Manual modification of this file is strongly discouraged, as it may result in errors, data inconsistency, or failed imports. For support or more information, please visit: https://github.com/HemantKArya/BloomeeTunes.',
      };

      // Write the media item data to a JSON file.
      final path = await writeToJSON(
          '${mediaItemDB.title}_BloomeeSong.json', mediaItemMap);
      log("Media item exported successfully", name: "FileManager");
      return path;
    } catch (e) {
      log("Error exporting media item: $e", name: "FileManager");
      return null;
    }
  }

  /// Imports a playlist from a JSON file.
  ///
  /// [filePath] - The path of the JSON file to import.
  /// Returns `true` if the import is successful, otherwise `false`.
  static Future<bool> importPlaylist(String filePath) async {
    try {
      // Read the playlist data from the JSON file.
      await readFromJSON(filePath).then((playlistMap) async {
        if (playlistMap != null && playlistMap.isNotEmpty) {
          // Validate the JSON structure.
          validatePlaylistJson(playlistMap);

          // Check if a playlist with the same name already exists.
          bool playlistExists =
              await isPlaylistExists(playlistMap['playlistName']);
          int i = 1;
          String playlistName = playlistMap['playlistName'];
          while (playlistExists) {
            playlistName = playlistMap['playlistName'] + "_$i";
            playlistExists = await isPlaylistExists(playlistName);
            i++;
          }
          log("Playlist name: $playlistName", name: "FileManager");

          // Add each media item in the playlist to the database.
          for (final mediaItemMap in playlistMap['mediaItems']) {
            final mediaItemDB = MediaItemDB.fromMap(mediaItemMap);
            await BloomeeDBService.addMediaItem(mediaItemDB, playlistName);
            log("Media item imported successfully - ${mediaItemDB.title}",
                name: "FileManager");
          }

          log("Playlist imported successfully");
        }
      });
      return true;
    } catch (e) {
      log("Invalid file format: $e");
      SnackbarService.showMessage(
          "Invalid file format. Please check the file and try again.");
      return false;
    }
  }

  /// Imports a single media item from a JSON file.
  ///
  /// [filePath] - The path of the JSON file to import.
  /// Returns `true` if the import is successful, otherwise `false`.
  static Future<bool> importMediaItem(String filePath) async {
    try {
      // Read the media item data from the JSON file.
      await readFromJSON(filePath).then((mediaItemMap) {
        if (mediaItemMap != null && mediaItemMap.isNotEmpty) {
          final mediaItemDB = MediaItemDB.fromMap(mediaItemMap);
          BloomeeDBService.addMediaItem(mediaItemDB, "Imported");
          log("Media item imported successfully");
        }
      });
      return true;
    } catch (e) {
      log("Invalid file format");
    }
    return false;
  }

  /// Automatically determines the type of JSON file (playlist or media item) and imports it.
  ///
  /// [filePath] - The path of the JSON file to import.
  /// Returns `true` if the import is successful, otherwise `false`.
  static Future<bool> importJSON(String filePath) async {
    try {
      final data = await readFromJSON(filePath);
      if (data == null || data.isEmpty) {
        throw const FormatException("Invalid or empty JSON file.");
      }

      if (data.containsKey('playlistName') && data.containsKey('mediaItems')) {
        // File is a playlist
        return await importPlaylist(filePath);
      } else if (data.containsKey('title') && data.containsKey('duration')) {
        // File is a media item
        return await importMediaItem(filePath);
      } else {
        throw const FormatException("Unrecognized JSON structure.");
      }
    } catch (e) {
      log("Error importing JSON file: $e", name: "FileManager");
      SnackbarService.showMessage(
          "Failed to import file. Please check the file and try again.");
      return false;
    }
  }

  /// Writes a map of data to a JSON file.
  ///
  /// [fileName] - The name of the file to create.
  /// [data] - The data to write to the file.
  /// Returns the file path of the created file, or `null` if an error occurs.
  static Future<String?> writeToJSON(String fileName, Map<String, dynamic> data,
      {String? path}) async {
    try {
      final filePath = path ?? (await getApplicationCacheDirectory()).path;
      final file = File('$filePath/$fileName');
      await file.writeAsString(jsonEncode(data));
      log("Data written to file: $filePath/$fileName", name: "FileManager");
      return '$filePath/$fileName';
    } catch (e) {
      log("Error writing file:", error: e, name: "FileManager");
      return null;
    }
  }

  /// Reads data from a JSON file and returns it as a map.
  ///
  /// [filePath] - The path of the file to read.
  /// Returns the data as a map, or `null` if an error occurs.
  static Future<Map<String, dynamic>?> readFromJSON(String filePath) async {
    try {
      final file = File(filePath);
      final data = await file.readAsString();
      log("Data read from file: $filePath", name: "FileManager");
      return jsonDecode(data);
    } catch (e) {
      log("Error reading file:", error: e);
      return null;
    }
  }

  /// Validates the structure of a playlist JSON file.
  /// Throws an error if the JSON file is not in the correct format or missing required fields.
  static void validatePlaylistJson(Map<String, dynamic> playlistMap) {
    if (!playlistMap.containsKey('playlistName') ||
        playlistMap['playlistName'] == null) {
      throw const FormatException("Invalid JSON: Missing 'playlistName'.");
    }

    if (!playlistMap.containsKey('mediaItems') ||
        playlistMap['mediaItems'] == null) {
      throw const FormatException("Invalid JSON: Missing 'mediaItems'.");
    }

    if (playlistMap['mediaItems'] is! List) {
      throw const FormatException("Invalid JSON: 'mediaItems' must be a list.");
    }

    for (final mediaItem in playlistMap['mediaItems']) {
      if (mediaItem is! Map<String, dynamic>) {
        throw const FormatException(
            "Invalid JSON: Each media item must be a map.");
      }
      validateMediaItemJson(mediaItem);
    }
  }

  // Validates mediaItem
  // Throws error if the JSON file is not correct
  static void validateMediaItemJson(Map<String, dynamic> mediaItem) {
    if (!mediaItem.containsKey('title') || mediaItem['title'] == null) {
      throw const FormatException(
          "Invalid JSON: Missing 'title' in a media item.");
    }

    if (!mediaItem.containsKey('duration') || mediaItem['duration'] == null) {
      throw const FormatException(
          "Invalid JSON: Missing 'duration' in a media item.");
    }

    if (!mediaItem.containsKey('permaURL') || mediaItem['permaURL'] == null) {
      throw const FormatException(
          "Invalid JSON: Missing 'permaURL' in a media item.");
    }

    if (!mediaItem.containsKey('streamingURL') ||
        mediaItem['streamingURL'] == null) {
      throw const FormatException(
          "Invalid JSON: Missing 'streamingURL' in a media item.");
    }

    if (!mediaItem.containsKey('album') || mediaItem['album'] == null) {
      throw const FormatException(
          "Invalid JSON: Missing 'album' in a media item.");
    }

    if (!mediaItem.containsKey('artist') || mediaItem['artist'] == null) {
      throw const FormatException(
          "Invalid JSON: Missing 'artist' in a media item.");
    }

    if (!mediaItem.containsKey('artURL') || mediaItem['artURL'] == null) {
      throw const FormatException(
          "Invalid JSON: Missing 'artURL' in a media item.");
    }
  }

  /// Export/Import M3U/M3U8 playlist
  static Future<String?> exportM3UPlaylist(
      String playlistName, List<MediaItemDB> mediaItems) async {
    final mediaPlaylistDB = await BloomeeDBService.getPlaylist(playlistName);
    if (mediaPlaylistDB != null) {
      try {
        // Fetch all media items in the playlist.
        List<MediaItemDB>? playlistItems =
            await BloomeeDBService.getPlaylistItems(mediaPlaylistDB);
        final packageInfo = await PackageInfo.fromPlatform();

        if (playlistItems != null) {
          // Prepare the playlist data for export.
          final Map<String, dynamic> playlistMap = {
            '_meta': {
              'generated_by':
                  'Bloomee - Open Source Music Streaming Application',
              'version':
                  'v${packageInfo.version}+${int.parse(packageInfo.buildNumber) % 1000}',
              'exportedAt': DateTime.now().toIso8601String(),
              'note':
                  'This file is automatically generated by Bloomee and is intended solely for importing playlists within the application. Manual modification of this file is strongly discouraged, as it may result in errors, data inconsistency, or failed imports. For support or more information, please visit: https://github.com/HemantKArya/BloomeeTunes.',
            },
            'playlistName': mediaPlaylistDB.playlistName,
            'mediaRanks': mediaPlaylistDB.mediaRanks,
            'mediaItems': playlistItems.map((e) => e.toMap()).toList(),
          };
          final String m3udata = convertJsonToM3U(playlistMap);
          // Write the playlist data to a JSON file.
          final path =
              await writeToM3U('${mediaPlaylistDB.playlistName}.m3u', m3udata);
          log("Playlist exported successfully", name: "FileManager");
          return path;
        }
      } catch (e) {
        log("Error exporting playlist: $e");
        return null;
      }
    } else {
      log("Playlist not found", name: "FileManager");
    }
    return null;
  }

  static Future<String?> writeToM3U(String fileName, String data) async {
    try {
      final filePath = (await getApplicationCacheDirectory()).path;
      final file = File('$filePath/$fileName');
      await file.writeAsString(data);
      log("Data written to file: $filePath/$fileName", name: "FileManager");
      return '$filePath/$fileName';
    } catch (e) {
      log("Error writing file:", error: e, name: "FileManager");
      return null;
    }
  }
}
